package com.starleague.battletech.rules.movement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

import com.starleague.StarLeagueException;
import com.starleague.battletech.BattletechException;
import com.starleague.battletech.game.BattletechPhase;
import com.starleague.battletech.game.IBattletechGame;
import com.starleague.battletech.game.IBattletechTeam;
import com.starleague.battletech.game.IMutableBattletechGame;
import com.starleague.battletech.game.IMutableBattletechTeam;
import com.starleague.battletech.hexagonal.BattletechHexTerrainType;
import com.starleague.battletech.hexagonal.IBattletechHex;
import com.starleague.battletech.hexagonal.IBattletechHexGrid;
import com.starleague.battletech.hexagonal.IBattletechHexTerrain;
import com.starleague.battletech.rules.AbstractPhaseState;
import com.starleague.battletech.rules.BattletechDice;
import com.starleague.battletech.rules.EndPhaseState;
import com.starleague.battletech.unit.HasEngine;
import com.starleague.battletech.unit.HasFacingDirection;
import com.starleague.battletech.unit.HasMutableMovement;
import com.starleague.battletech.unit.IBattletechUnit;
import com.starleague.battletech.unit.IMutableBattletechUnit;
import com.starleague.battletech.unit.mech.HasMutableMechMovement;
import com.starleague.battletech.unit.mech.IMutableMech;
import com.starleague.battletech.unit.mech.MechMovementActionType;
import com.starleague.battletech.unit.mech.MechMovementHelper;
import com.starleague.battletech.unit.mech.MechMovementMode;
import com.starleague.hexagonal.HexException;
import com.starleague.hexagonal.IHexCoordinate;
import com.starleague.hexagonal.flat.FlatHexDirection;
import com.starleague.hexagonal.flat.FlatHexGridUtil;

@Singleton
@Named
public class MovementPhaseState
    extends AbstractPhaseState
{
    private static final Logger log = LoggerFactory.getLogger(MovementPhaseState.class);
    private static final FlatHexGridUtil flatHexGridUtil = new FlatHexGridUtil();

    @Inject
    public MovementPhaseState(BattletechDice dice,
                              EndPhaseState endPhaseState)
    {
        super(dice, BattletechPhase.MOVEMENT, endPhaseState, BattletechPhase.INITIATIVE);
    }

    @Override
    public void startPhase(IMutableBattletechGame game)
        throws StarLeagueException
    {
        super.startPhase(game);

        for (IMutableBattletechUnit unit : game.getUnits())
            if (unit instanceof HasMutableMovement)
                ((HasMutableMovement) unit).clearMovement();

        game.setCurrentTeam(null);
        game.setCurrentUnit(null);

        // redundant
        for (IMutableBattletechTeam team : game.getTeams())
            team.setUnitsToMove(0);

        setUnitsToMove(game.getTeams());

        IMutableBattletechTeam currentTeam = calcCurrentTeam(game);
        if (currentTeam == null)
            endPhase(game);
        else
        {
            game.setCurrentTeam(currentTeam);
            log.debug("Initial Team must move units [{}]: {}", currentTeam.getUnitsToMove(), currentTeam);
        }
    }

    @Override
    protected void endPhase(IMutableBattletechGame game)
        throws StarLeagueException
    {
        // both redundant
        game.setCurrentTeam(null);
        game.setCurrentUnit(null);

        // Probably redundant
        for (IMutableBattletechTeam team : game.getTeams())
            team.setUnitsToMove(0);

        super.endPhase(game);
    }

    private void next(IMutableBattletechGame game)
        throws StarLeagueException
    {
        IMutableBattletechTeam currentTeam = game.getCurrentTeam();
        IMutableBattletechUnit currentUnit = game.getCurrentUnit();

        // Redundant
        Preconditions.checkState((currentUnit != null),
                                 "Cannot move to next Team until current Team [{}] moves a Unit: {}",
                                 currentTeam,
                                 game);

        // Redundant
        Preconditions.checkState((currentUnit.getTeam() == currentTeam),
                                 "Current Unit [{}] is not a part of the current Team [{}]: {}",
                                 currentUnit,
                                 currentTeam,
                                 game);

        game.setCurrentUnit(null);
        currentTeam.setUnitsToMove(currentTeam.getUnitsToMove() - 1);

        IMutableBattletechTeam nextTeam = calcCurrentTeam(game);
        if (nextTeam == currentTeam)
        {
            log.debug("Current Team has more units to move: {}", currentTeam);
            return;
        }

        if (nextTeam != null)
        {
            game.setCurrentTeam(nextTeam);
            log.debug("Second Team must now move units [{}]: {}", nextTeam.getUnitsToMove(), nextTeam);
            return;
        }

        // Recalculate the number of units to move again
        setUnitsToMove(game.getTeams());

        currentTeam = calcCurrentTeam(game);
        if (currentTeam == null)
            endPhase(game);
        else
        {
            game.setCurrentTeam(currentTeam);
            log.debug("First Team must now move units [{}]: {}", currentTeam.getUnitsToMove(), currentTeam);
        }
    }

    public void mechWalk(IMutableBattletechGame game,
                         IMutableMech mech,
                         MechMovementActionType... movementActionTypes)
        throws StarLeagueException
    {
        mechWalk(game, mech, Lists.newArrayList(movementActionTypes));
    }

    public void mechWalk(IMutableBattletechGame game,
                         IMutableMech mech,
                         List<MechMovementActionType> movementActionTypeList)
        throws StarLeagueException
    {
        mechMove(game, mech, MechMovementMode.WALK, movementActionTypeList);
    }

    public void mechRun(IMutableBattletechGame game,
                        IMutableMech mech,
                        MechMovementActionType... movementActionTypes)
        throws StarLeagueException
    {
        mechRun(game, mech, Lists.newArrayList(movementActionTypes));
    }

    public void mechRun(IMutableBattletechGame game,
                        IMutableMech mech,
                        List<MechMovementActionType> movementActionTypeList)
        throws StarLeagueException
    {
        mechMove(game, mech, MechMovementMode.RUN, movementActionTypeList);
    }

    public void mechStandStill(IMutableBattletechGame game,
                               IMutableMech mech)
        throws StarLeagueException
    {
        mechMove(game, mech, MechMovementMode.STAND_STILL, Lists.<MechMovementActionType> newArrayList());
    }

    public void mechJump(IMutableBattletechGame game,
                         IMutableMech mech,
                         IHexCoordinate hexCoordinate,
                         FlatHexDirection facing) // throws StarLeagueException
    {
        //
    }

    private IMutableBattletechTeam calcCurrentTeam(IMutableBattletechGame game)
    {
        List<? extends IMutableBattletechTeam> initiativeTeams = Lists.newArrayList(game.getTeams());
        Collections.sort(initiativeTeams, IBattletechTeam.INITIATIVE_COMPARATOR);
        IMutableBattletechTeam currentTeam = initiativeTeams.get(0);

        if (currentTeam.getUnitsToMove() > 0)
            return currentTeam;

        currentTeam = getOtherTeam(initiativeTeams, currentTeam);
        if (currentTeam.getUnitsToMove() > 0)
            return currentTeam;

        return null;
    }

    private void mechMove(IMutableBattletechGame game,
                          IMutableMech mech,
                          MechMovementMode movementMode,
                          List<MechMovementActionType> movementActionTypeList)
        throws StarLeagueException
    {
        verifyPhase(game);
        verifyMech(game, mech, movementMode);

        MechMovementState mechState = new MechMovementState(mech, movementMode);
        List<MechMovementAction> movementActionList = new ArrayList<MechMovementAction>(movementActionTypeList.size());

        int actionIndex = 0;
        for (final MechMovementActionType movementActionType : movementActionTypeList)
        {
            final MechMovementAction mechMovementAction;

            // TODO: Depending on model, do different things (assert StandingStill=noMovement; Jump=FreeTurning)
            // Pretend to run the actions
            // TODO: Interrupt if the user wants to avoid PilotingSkill rolls.
            switch (movementActionType)
            {
                case TURN_LEFT:
                    mechMovementAction = new MechMovementActionTurnLeft(game, movementActionType, actionIndex);
                    break;

                case TURN_RIGHT:
                    mechMovementAction = new MechMovementActionTurnRight(game, movementActionType, actionIndex);
                    break;

                case MOVE_FORWARD:
                case MOVE_FORWARD_LEFT:
                case MOVE_FORWARD_RIGHT:
                case MOVE_BACKWARD:
                    mechMovementAction = new MechMovementActionMoveForward(game, movementActionType, actionIndex);
                    break;

                // TODO: check restrictions here
                default:
                    throw new BattletechException("Unknown MechMovementActionType: " + movementActionType);
            }

            // Pretend to run the actions
            // TODO: Interrupt if the user wants to avoid PilotingSkill rolls.
            mechMovementAction.execute(mechState);

            movementActionList.add(mechMovementAction);
            ++actionIndex;
        }

        game.setCurrentUnit(mech);
        mech.setMovementMode(movementMode);

        try
        {
            // Run the actions
            for (MechMovementAction movementAction : movementActionList)
            {
                // TODO: Interrupt on a PilotingSkillRollException
                movementAction.execute(mech);
            }
        }
        finally
        {
            int maximumMovementPoints = MechMovementHelper.calcMaximumMp(mech);
            if (mech.getUsedMovementPoints() > maximumMovementPoints)
                mech.setUsedMovementPoints(maximumMovementPoints);
        }
        // Verify movement rules (invalid terrain, can't change movement-mode, can't walk, etc...)

        // TODO: Audit Moves, including failed (but not invalid).
        // Invalid moves should kick-back to the client with a warning.

        log.info("Mech Movement Completed: {}", mech);

        // If all movement actions succeed, go to the next unit or team.

        next(game);
    }

    <MECH extends IBattletechUnit & HasEngine & HasMutableMechMovement> void turnMechFacing(int actionIndex,
                                                                                            MECH mech,
                                                                                            MechMovementActionType movementActionType)
        throws BattletechException
    {
        int maximumMovementPoints = MechMovementHelper.calcMaximumMp(mech);
        if (maximumMovementPoints <= 0)
        {
            throw new BattletechException("Mech has no MP: " + mech);
        }

        int totalMpUsed = mech.getUsedMovementPoints() + 1;

        if (totalMpUsed > maximumMovementPoints)
        {
            boolean isMinimumMovement = (mech.getUsedMovementPoints() == 0)
                                        && (mech.getMovementMode() == MechMovementMode.RUN);
            if (!isMinimumMovement)
            {
                throw new BattletechException("Not enough MP for index [" + actionIndex + "]: " + movementActionType);
            }
        }

        mech.setFacingDirection(calcTurnFacingDirection(mech, movementActionType));
        mech.setUsedMovementPoints(mech.getUsedMovementPoints() + 1);
    }

    <MECH extends IMutableBattletechUnit & HasEngine & HasMutableMechMovement> void moveMechForward(int actionIndex,
                                                                                                    IBattletechHexGrid hexGrid,
                                                                                                    MECH mech,
                                                                                                    MechMovementActionType movementActionType)
        throws BattletechException, HexException
    {
        int maximumMovementPoints = MechMovementHelper.calcMaximumMp(mech);
        if (maximumMovementPoints <= 0)
        {
            throw new BattletechException("Mech has no MP: " + mech);
        }

        // TODO: Verify Mech is in any condition to move forward (Is standing, etc...),
        IHexCoordinate srcHexCoordinate = mech.getHexCoordinate();
        IHexCoordinate destCoordinate = calcDestCoordinate(movementActionType,
                                                           mech.getHexCoordinate(),
                                                           mech.getFacingDirection());

        IBattletechHex srcHex = hexGrid.getHexAt(srcHexCoordinate);
        IBattletechHex destHex = hexGrid.getHexAt(destCoordinate);

        // TODO: srcElevation should be based on the Mech, not the Hex (in some cases, like Roads, bridges, etc...)
        int srcElevation = calcElevation(srcHex);
        int destElevation = calcElevation(destHex);

        verifyMovement(mech, movementActionType, srcHex, destHex, srcElevation, destElevation);
        int movementPointCost = calcMovementPointCost(mech, srcHex, destHex, srcElevation, destElevation);
        int totalMpUsed = mech.getUsedMovementPoints() + movementPointCost;

        // TODO: Standing up is allowed by MinimumMovementRule
        // TODO: MinimumMovementRule for MOVE_FORWARD_LEFT/RIGHT ??
        // TODO: MinimumMovementRule for moving BACKWARD?
        // boolean isMinimumMovement = (actionIndex == 0)
        // && (maximumMovementPoints > 0)
        // && (mech.getUsedMovementPoints() == 0)
        // && (movementMode == MechMovementMode.RUN)
        // && (movementActionType == MechMovementActionType.MOVE_FORWARD);
        if (totalMpUsed > maximumMovementPoints)
        {
            boolean isMinimumMovement = (mech.getUsedMovementPoints() == 0)
                                        && (mech.getMovementMode() == MechMovementMode.RUN);
            if (!isMinimumMovement)
            {
                throw new BattletechException("Not enough MP for index [" + actionIndex + "]: " + movementActionType);
            }
        }

        // TODO: Terrain, gyro, piloting checks; Maybe in a passed-in Piloting-skill-roller object, that is different
        // depending on if it is a Mech or a MechState we are operating on.
        mech.setHexCoordinate(destCoordinate);
        mech.setElevation(destElevation);
        mech.setUsedMovementPoints(totalMpUsed);
    }

    private static <UNIT extends HasFacingDirection> FlatHexDirection calcTurnFacingDirection(UNIT unit,
                                                                                              MechMovementActionType movementActionType)
        throws BattletechException
    {

        switch (movementActionType)
        {
            case TURN_LEFT:
                return unit.getFacingDirection().getLeft();
            case TURN_RIGHT:
                return unit.getFacingDirection().getRight();
            case MOVE_BACKWARD:
            case MOVE_FORWARD:
            case MOVE_FORWARD_LEFT:
            case MOVE_FORWARD_RIGHT:
            default:
                throw new BattletechException("Invalid MechMovementActionType for calculating Facing Direction: "
                                              + movementActionType);
        }
    }

    private static IHexCoordinate calcDestCoordinate(MechMovementActionType movementActionType,
                                                     IHexCoordinate srcHexCoordinate,
                                                     FlatHexDirection facingDirection)
        throws BattletechException
    {
        switch (movementActionType)
        {
            case MOVE_FORWARD:
                return flatHexGridUtil.getHexCoordinateInDirection(srcHexCoordinate, facingDirection);
            case MOVE_BACKWARD:
                return flatHexGridUtil.getHexCoordinateInDirection(srcHexCoordinate, facingDirection.getOpposite());
            case TURN_LEFT:
            case TURN_RIGHT:
                return srcHexCoordinate;
            case MOVE_FORWARD_LEFT:
            case MOVE_FORWARD_RIGHT:
            default:
                throw new BattletechException("Unsupported MechMovementActionType: " + movementActionType);
        }
    }

    private static <MECH extends HasMutableMechMovement> void verifyMovement(MECH mech,
                                                                             MechMovementActionType movementActionType,
                                                                             IBattletechHex srcHex,
                                                                             IBattletechHex destHex,
                                                                             int srcElevation,
                                                                             int destElevation)
        throws BattletechException
    {
        final MechMovementMode mechMovementMode = mech.getMovementMode();

        if ((movementActionType == MechMovementActionType.MOVE_BACKWARD) && (mechMovementMode == MechMovementMode.RUN))
            throw new BattletechException("Mech cannot run backward");

        int elevationChange = Math.abs(destElevation - srcElevation);
        if (elevationChange > 2)
            throw new BattletechException("Elevation change is too great for a Mech to move ["
                                          + elevationChange
                                          + "] from srcHex: "
                                          + srcHex
                                          + "\nto destHex: "
                                          + destHex);

        if ((movementActionType == MechMovementActionType.MOVE_BACKWARD) && (elevationChange > 0))
            throw new BattletechException("Elevation change is too great for a Mech to move backward ["
                                          + elevationChange
                                          + "] from srcHex: "
                                          + srcHex
                                          + "\nto destHex: "
                                          + destHex);

        for (IBattletechHexTerrain destHexTerrain : destHex.getHexTerrainList())
            switch (destHexTerrain.getHexTerrainType())
            {
                case PLAINS:
                case ROUGH:
                case WOODS:
                    break;

                case WATER:
                    switch (mechMovementMode)
                    {
                        case RUN:
                            if (destHexTerrain.getLevel() > 0)
                                throw new BattletechException("Mech cannot run through Water");
                            break;

                        case WALK:
                            break;

                        case STAND_STILL:
                        case JUMP:
                        default:
                            throw new BattletechException("Unsupported MechMovementMode: " + mechMovementMode);

                    }
                    if ((destHexTerrain.getLevel() > 0) && (mech.getMovementMode() == MechMovementMode.RUN))
                        throw new BattletechException("Mech cannot run through Water");
                    break;

                default:
                    throw new BattletechException("Unsupported BattletechHexTerrainType: "
                                                  + destHexTerrain.getHexTerrainType());
            }
    }

    private static <MECH extends HasMutableMechMovement> int calcMovementPointCost(MECH mech,
                                                                                   IBattletechHex srcHex,
                                                                                   IBattletechHex destHex,
                                                                                   int srcElevation,
                                                                                   int destElevation)
        throws BattletechException
    {
        int movementPointCost = 1;

        int elevationChange = Math.abs(destElevation - srcElevation);
        if (elevationChange > 2)
            throw new BattletechException("Elevation change is too great for a Mech to move forward ["
                                          + elevationChange
                                          + "] from srcHex: "
                                          + srcHex
                                          + "\nto destHex: "
                                          + destHex);

        movementPointCost += elevationChange;

        for (IBattletechHexTerrain destHexTerrain : destHex.getHexTerrainList())
            movementPointCost += calcMovementPointCostForTerrain(destHexTerrain);

        return movementPointCost;
    }

    private static int calcElevation(IBattletechHex hex)
    {
        int elevation = hex.getElevation();

        IBattletechHexTerrain hexTerrainWater = findHexTerrainOfType(hex, BattletechHexTerrainType.WATER);
        if (hexTerrainWater != null)
            elevation -= hexTerrainWater.getLevel();

        return elevation;
    }

    private static int calcMovementPointCostForTerrain(IBattletechHexTerrain hexTerrain)
        throws BattletechException
    {
        switch (hexTerrain.getHexTerrainType())
        {
            case PLAINS:
                return 0;
            case ROUGH:
                return 1;
            case WATER:
                return (hexTerrain.getLevel() <= 1) ? hexTerrain.getLevel() : 3;
            case WOODS:
                return hexTerrain.getLevel();
            default:
                throw new BattletechException("Unsupported BattletechHexTerrainType: " + hexTerrain.getHexTerrainType());
        }
    }

    private static Collection<? extends IMutableBattletechUnit> getMovableUnitsForTeam(final IMutableBattletechTeam team)
    {
        return Collections2.filter(team.getGame().getUnits(), Predicates.and(onTeamPredicate(team), UNIT_CAN_MOVE));
    }

    private static void setUnitsToMove(Collection<? extends IMutableBattletechTeam> teams)
    {
        // TODO: What should we do here, as Movement is slightly different than Firing?
        // Some units may NOT be movable units, and some units may not be fireable units.
        // Or should we just lump them all together, and let immobile units count towards a side's movable units?

        // Redundant
        Preconditions.checkState((teams.size() == 2), "Must have exactly 2 Teams, Found [%s]", teams.size());

        Iterator<? extends IMutableBattletechTeam> teamIter = teams.iterator();
        IMutableBattletechTeam teamA = teamIter.next();
        IMutableBattletechTeam teamB = teamIter.next();

        int unitCountA = getMovableUnitsForTeam(teamA).size();
        int unitCountB = getMovableUnitsForTeam(teamB).size();

        if (unitCountA == 0)
        {
            teamA.setUnitsToMove(0);
            teamB.setUnitsToMove(unitCountB);
        }
        else if (unitCountB == 0)
        {
            teamA.setUnitsToMove(unitCountA);
            teamB.setUnitsToMove(0);
        }
        else
        {
            // Integer truncation (in division) is intentional
            teamA.setUnitsToMove((unitCountA > unitCountB) ? (unitCountA / unitCountB) : 1);
            teamB.setUnitsToMove((unitCountB > unitCountA) ? (unitCountB / unitCountA) : 1);
        }
    }

    private static IMutableBattletechTeam getOtherTeam(Collection<? extends IMutableBattletechTeam> teams,
                                                       IMutableBattletechTeam team)
    {
        // Redundant
        Preconditions.checkArgument(teams.contains(team),
                                    "current Team [%s] does not exist in Game: %s",
                                    team,
                                    team.getGame());

        for (IMutableBattletechTeam otherTeam : teams)
            if (otherTeam != team)
                return otherTeam;

        // Impossible
        throw new RuntimeException("Cannot find another Team other than [" + team + "] in: " + team.getGame());
    }

    private static final Predicate<IMutableBattletechUnit> UNIT_CAN_MOVE = new Predicate<IMutableBattletechUnit>()
    {
        @Override
        public boolean apply(IMutableBattletechUnit unit)
        {
            // TODO: Is the unit destroyed? Do temporarily shutdown units count for the Movement Phase?
            // if (isActiveUnit(unit) &&
            if (unit instanceof HasMutableMovement)
            {
                HasMutableMovement hasMovement = (HasMutableMovement) unit;
                return !hasMovement.hasMoved();
            }
            return false;
        }
    };

    private static void verifyMech(IMutableBattletechGame game,
                                   IMutableMech mech,
                                   MechMovementMode newMovementMode)
        throws BattletechException
    {
        // TODO: Split this into a base-class method that determines if a Mech is valid
        // (has a team, hex, direction, etc...)
        // And movement-specific checks
        if (mech.getTeam() == null)
            // TODO: Severe Exception
            throw new BattletechException("Cannot move Mech (mech has no team): " + mech);

        if ((game.getCurrentTeam() != mech.getTeam()))
            throw new BattletechException("Cannot move Mech (mech on wrong team): " + mech);

        IMutableBattletechUnit currentUnit = game.getCurrentUnit();
        if (currentUnit != null)
        {
            if (currentUnit != mech)
                throw new BattletechException("Cannot move Mech (not the current unit): " + mech);

            // TODO: Someday this might be a problem:
            // TODO: Preventing a user from changing movement mode (even if they haven't moved)
            MechMovementMode currentMovementMode = mech.getMovementMode();
            if (currentMovementMode == null)
                log.warn("Mech is the CurrentUnit, but it has no MovementMode");
            else if (currentMovementMode != newMovementMode)
                throw new BattletechException("Cannot move Mech using mode ["
                                              + newMovementMode
                                              + "]. Mode already set to ["
                                              + currentMovementMode
                                              + "]: "
                                              + mech);
        }
        else if (mech.hasMoved())
            throw new BattletechException("Cannot move Mech (already moved): " + mech);
    }

    private abstract class MechMovementAction
    {
        protected final IBattletechGame game;
        protected final MechMovementActionType movementActionType;
        protected final int actionIndex;

        protected MechMovementAction(IBattletechGame game,
                                     MechMovementActionType movementActionType,
                                     int actionIndex)
        {
            this.game = game;
            this.movementActionType = movementActionType;
            this.actionIndex = actionIndex;
        }

        abstract void execute(MechMovementState mechState)
            throws StarLeagueException;

        abstract void execute(IMutableMech mech)
            throws StarLeagueException;
    }

    private class MechMovementActionTurnLeft
        extends MechMovementAction
    {
        MechMovementActionTurnLeft(IBattletechGame game,
                                   MechMovementActionType movementActionType,
                                   int actionIndex)
        {
            super(game, movementActionType, actionIndex);
        }

        @Override
        void execute(MechMovementState mechState)
            throws BattletechException
        {
            turnMechFacing(this.actionIndex, mechState, this.movementActionType);
        }

        @Override
        void execute(IMutableMech mech)
            throws BattletechException
        {
            turnMechFacing(this.actionIndex, mech, this.movementActionType);
        }
    }

    private class MechMovementActionTurnRight
        extends MechMovementAction
    {
        MechMovementActionTurnRight(IBattletechGame game,
                                    MechMovementActionType movementActionType,
                                    int actionIndex)
        {
            super(game, movementActionType, actionIndex);
        }

        @Override
        void execute(MechMovementState mechState)
            throws BattletechException
        {
            turnMechFacing(this.actionIndex, mechState, this.movementActionType);
        }

        @Override
        void execute(IMutableMech mech)
            throws BattletechException
        {
            turnMechFacing(this.actionIndex, mech, this.movementActionType);
        }
    }

    private class MechMovementActionMoveForward
        extends MechMovementAction
    {
        MechMovementActionMoveForward(IBattletechGame game,
                                      MechMovementActionType movementActionType,
                                      int actionIndex)
        {
            super(game, movementActionType, actionIndex);
        }

        @Override
        void execute(MechMovementState mechState)
            throws BattletechException, HexException
        {
            moveMechForward(this.actionIndex, this.game.getHexGrid(), mechState, this.movementActionType);
        }

        @Override
        void execute(IMutableMech mech)
            throws BattletechException, HexException
        {
            moveMechForward(this.actionIndex, this.game.getHexGrid(), mech, this.movementActionType);
        }
    }
}
